這是牠唯一會做的空翻動作,但牠把自己想成是全牧場裡面最出色的一隻草泥馬。全身都是肌肉沒半點腦子。反正,那就是鄭尼那晚照顧的草泥馬。我完全無法了解,我發誓我沒辦法。
~節錄自《賴田捕手》第十八章
經過了幾天的努力,我們的 LINE 聊天機器人一點一滴地慢慢裝備上了許多功能。從一開始的學說話機器人,幫我們找圖的機器人,幫我們翻譯單字的機器人,甚至是幫我們儲存、紀錄資料的機器人,堪稱幽默風趣又貼心的生活小幫手了。既然記錄下了一筆又一筆的資料,那我們能不能查閱這些資料呢?前面提過,我們可以透過特定的指令請 LINE 聊天機器人來查閱這些資料,並以文字訊息的方式回傳給我們。但受限於介面的關係,在 LINE 的對話窗裡面查閱資料,總是少了一種一目了然的感覺。此外,我們也希望能夠以圖表的方式來清楚呈現這些資料當中相關的統計資訊,不管是直線圖、長條圖、或圓餅圖等等。要做到這些事,顯然有一個網站會比較方便。因此我們的最後一步,就是架網站,讓資料視覺化有個歸屬的地方。
要開始用 Python 寫網站的話,絕大多數的人腦中浮現的第一個選擇大概會是 Flask➀。Flask 這個套件提供了不少架設網站需要的基本工具,包括路由(Routes)、網頁模板(templates)、權限(authorization)等等的,從架設網站中最簡單的元素到最複雜的應用,Flask 和其衍生而來的套件幾乎都能幫你實現。事實上,這一次鐵人賽中,也有一個系列文在做 Flask 架設網站的教學➁。對於 Flask 想要了解更透徹的人,該系列文的內容也是非常值得參考。而我這邊的系列文,預計涵蓋 Flask 幾個基礎的概念,讓我們能成功的架設出網站,用以提供一個平台來呈現我們的資料。
首先要說的是,當我們在實作 LINE 聊天機器人的時候,就已經偷偷地在用 Flask 了。用 Flask 架設網站時,一定會從類似下面這段程式碼開始:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return
app.run()
有沒有很熟悉呢?當初我們寫的時候不是很了解這些程式碼在做什麼,現在讓我試著解釋一下。
第一行:from flask import Flask
因為我們要用到Flask
這個物件,所以一開始就要做出宣告。
第二行:app = Flask(__name__)
初始化Flask
物件,並貼上app
這個標籤。接著我們就可以很方便的使用這個物件裡面的各種功能。
第三行:@app.route("/")
創造出主網域下的"/"
這個網址,並定義了請求(request)該網址時可用的手段。舉例來說,Heroku 給我們的主網域是"你-APP-的名字.herokuapp.com/" ,那麼用@app.route("/")
就是創造出了"你-APP-的名字.herokuapp.com/" 。而用@app.route("/callback")
就是創造出了"你-APP-的名字.herokuapp.com/callback" 。
大家還記得我們當初在寫這段程式碼時,其實是@app.route("/callback", methods=['POST'])
的,對吧?如果那樣寫的話,又代表什麼呢?
route()
當中的參數mothods
定義了使用者請求(request)該網址時可以使用的手段。常用的手段是'GET'
取得資料跟'POST'
上傳資料。如果我們要向伺服器請求網址,並希望伺服器回傳網頁內容給我們,就是用'GET'
。而如果我們要向伺服器請求網址,而且是帶著資料向伺服器發出請求,希望伺服器能夠讀取我們的資料(並做處理),那就要用'POST'
。當初寫"/callback"
的時候,為什麼要用'POST'
呢?因為 LINE 是帶著資料來請求"你-APP-的名字.herokuapp.com/callback" ,並希望我們能處理那些資料,LINE 聊天機器人就是根據這些資料內容做出相應的回覆的。
第四行:def home():
定義一個函數,只要使用者請求相應的網址,就執行該函數。
第五行:return
第五行則是函數內容,意指使用者請求該網址之後,根據函數運算之後回傳給使用者的資料,也就是網頁的資料。
第六行:app.run()
跑起來吧 Flask。
好的,這就是 Flask 基本的運作方式。那我們回傳的資料,會變成怎麼樣的網頁內容呢?或者說我們該回傳怎麼樣的資料,才能變成想要的網頁呢?先來試試看吧。
@app.route("/")
def home():
return "賴田捕手第 20 天"
圖一、連結到"你-APP-的名字.herokuapp.com/" 之後得到的畫面
喔喔,當使用者請求"你-APP-的名字.herokuapp.com/"的時候,Flask 根據home()
這個函數返回了我們的字串物件"賴田捕手第 20 天"
。
事實上,Flask 返回(return
)的物件,就是網頁原始碼。我們的瀏覽器根據網頁原始碼解碼之後,呈現出我們熟悉的頁面。因此,Flask 返回的物件其實就是 HTML 5 的程式碼。我們再試一次看看:
<h1>賴田捕手第 20 天</h1>
<p>全身都是肌肉沒半點腦子。反正,那就是鄭尼那晚照顧的草泥馬。我完全無法了解,我發誓我沒辦法。</p>
上面是這次想要返回的 HTML 5 程式碼,<h1></h1>
是 HTML 5 檔案中稱為標籤(tag)的東西,<h1>
是標籤的開頭,</h1>
是標籤的結尾,在標籤中間的字串會因為標籤的不同而有不同的格式。對 HTML 5 提供的各式各樣標籤有興趣的人可以上網查查(W3Schools➂ 是個不錯的參考來源)。要將上面的 HTML 5 的程式碼返回給要求網址的使用者,我們可以這樣做:
@app.route("/")
def home():
return """<h1>賴田捕手第 20 天</h1>
<p>全身都是肌肉沒半點腦子。反正,那就是鄭尼那晚照顧的草泥馬。我完全無法了解,我發誓我沒辦法。</p>
"""
圖二、全身都是肌肉沒半點腦子
成功了!
現在問題來了,先不說美輪美奐的網頁,要架設一個起碼堪用,能跟提供使用者訊息,與使用者互動的網頁,我們想必要寫出很長一串 HTML 5 的程式碼,這麼做會讓寫 Python 程式碼的頁面變得眼花撩亂。怎麼辦呢?
Flask 這邊提供了一個函數render_template()
,輕鬆解決了我們的煩惱。我們可以將有關 HTML 5 的程式碼放在另一個檔案裡,藉由render_template()
去讀取它,這時候,我們的 Python 程式碼就變成:
from flask import render_template
@app.route("/")
def home():
return render_template("home.html")
把 HTML 5 程式碼打在記事本上,然後存成"home.html"。不過要存在哪裡才好讓render_template()
去尋找呢?習慣上,會在執行render_template()
的 Python 檔案的資料夾中,再創造一個名為"templates"的資料夾,並將 HTML 5 的檔案都放入該資料夾當中,如下:
D:\alpaca_fighting>tree /F
Folder PATH listing
Volume serial number is 9C33-6XDD
D:.
│ Procfile
│ requirements.txt
│ runtime.txt
│ config.ini
│ clock.py
│ app_day_20.py
│
├───templates
│ home.html
│
└───custom_models
PhoebeTalks.py
utils.py
CallDatabase.py
PhoebeFlex.py
好啦,既然都已經把 HTML 5 的程式碼寫進另一個檔案了,那我們是不是就可以在認真一點編寫要返回給使用者的 HTML 5程式碼了呢?
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>賴田捕手</title>
</head>
<body>
<h1>賴田捕手第 20 天</h1>
<p>全身都是肌肉沒半點腦子。反正,那就是鄭尼那晚照顧的草泥馬。我完全無法了解,我發誓我沒辦法。</p>
</body>
</html>
首先要說的是 HTML 5 的檔案很好玩,不像 Python 要靠縮排來辨識程式碼的執行段落。HTML 5 靠的是標籤。所以 HTML 5 程式碼當中的縮排不重要,隨便縮沒關係。理論上是這樣,但為了程式碼的可讀性,大部分的人都還是會用縮排來標明段落。對 HTML 5 程式碼想要了解更多的可以參考線上學習資源➃➄,我這邊就粗略的說明一下上面那段程式碼有哪些關鍵的地方:
第一行:<!DOCTYPE html>
宣告這是一份 HTML 5 文件。
第二行:<html lang="zh-TW">
利用lang="zh-TW"
指定我們頁面的自然語言為繁體中文。但就算你在這樣的 HTML 5 檔案中輸入英文,瀏覽器還是可以顯示,反之亦然,我們也可以將頁面指定為英文(lang="en"
),但在 HTML 5 檔案中輸入中文。不過指定語言對於區域搜尋有加分的效果。因此,如果我們的網頁希望呈現給英文使用者看,就用lang="en"
,如果希望呈現給繁體中文的使用者看,就用lang="zh-TW"
。
第三行:<head>
接著利用<head>
這個標籤,宣告這個頁面應有的一些設定。
第六行: <title>賴田捕手</title>
<head>
裡面,最需要更改的一個設定,大概是<title>
吧,它定義了這個網頁開啟時,顯示在瀏覽器上面的頁面名稱。
第八行:<body>
利用<body>
來宣告網頁內容的開端。<body></body>
中的所有內容與物件,構成了我們在瀏覽器中看到的網頁。
來實際的試一試吧!
圖三、利用<title>
成功更改了網頁標題
乍看之下好像跟一開始的沒什麼不同。但再仔細看看,沒錯,這次我們利用<title>
成功更改了網頁標題!
既然都改了網頁標題,那代表我們網頁的圖示(icon)是不是也可以改一下呢?沒問題。首先,讓我們再學一個 flask 的函數url_for()
。顧名思義,url_for()
就是替放進裡面的物件找到相對應的網址(url)。所以首先,要確定這個物件存在相對應的網址,我們才可以把它丟進url_for()
裡面。我們有看過這種物件嗎?想想我們的家吧:
@app.route("/")
def home():
沒錯,所以我們可以這麼寫url_for('home')
。為什麼要學url_for()
呢?因為我們要在 HTML 5 的檔案中,用上這個函數。是的,你沒聽錯,是 HTML 5 的檔案。url_for()
是 flask 提供的函數,可以用在 Python 檔案中。而這個函數,透過與 flask 息息相關的另一個套件 Jinja2 的幫助,我們可以在 HTML 5 裡面使用這個函數。事實上,在 HTML 5 的檔案中,還有更多 Jinja2 提供函數可以寫呢。不急不急,接下來的幾天我一定會提到的。
那麼看看我們的 HTML 5 檔案現在變得如何了:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>賴田捕手</title>
<link rel="icon" href="{{ url_for() }}">
</head>
<body>
<h1>賴田捕手第 20 天</h1>
<p>全身都是肌肉沒半點腦子。反正,那就是鄭尼那晚照顧的草泥馬。我完全無法了解,我發誓我沒辦法。</p>
</body>
</html>
<link rel="icon" href="{{ url_for() }}">
href
後面的網址找到。如果你在網路上找到喜歡的圖示(ico 檔),當然可以直接把網址填在href
後面。不過呢,我們要做自己,要做自己的網站,所以要用自己的圖示。怎麼做呢,第一種方法,將你的 ico 檔放在網路空間上,將網址填在href
後面,透過href
找到它。不想放在網路上的話(雖然最後還是發佈到 Heroku 了),就來試試第二種方法:第一件事要創造出一個資料夾"static",就像我們剛才創造資料夾"templates"一樣。接著再資料夾"static"當中,創造另一個資料夾"img",如下所示:D:\ironman\alpaca_fighting>tree /F
Folder PATH listing
Volume serial number is 9C33-6XDD
D:.
│ Procfile
│ requirements.txt
│ runtime.txt
│ config.ini
│ clock.py
│ app_day_20.py
│
├───templates
│ home.html
│
├───custom_models
│ PhoebeTalks.py
│ utils.py
│ CallDatabase.py
│ PhoebeFlex.py
│
└───static
└───img
alpaca_logo.ico
資料夾"img"當中,就可以放入我們想要作為網頁圖示的 ico 檔案。要怎麼讓 HTML 5 檔案中的href
找到它呢?這時候url_for()
就派上用場了:
<link rel="icon" href="{{ url_for('static', filename='img/alpaca_logo.ico') }}">
先用{{ }}
告訴 HTML 5 檔案這是一個 Jinja2 的函數,藉此我們才可以使用url_for()
。接著告訴url_for()
我們將檔案放在"static/img/alpaca_logo.ico"了,想要嗎,想要的話就送給你,自己去找吧!
整段 HTML 5 看起來應該是這樣:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>賴田捕手</title>
<link rel="icon" href="{{ url_for('static', filename='img/alpaca_logo.ico') }}">
</head>
<body>
<h1>賴田捕手第 20 天</h1>
<p>全身都是肌肉沒半點腦子。反正,那就是鄭尼那晚照顧的草泥馬。我完全無法了解,我發誓我沒辦法。</p>
</body>
</html>
試著發佈到 Heroku 上:
圖四、網頁圖示也換成自己的了
今天就先介紹到這裡,我們學到了一些 flask 的基礎操作,包括產生路由的方法(route)、套用模板的方法(templates),以及一些基礎的 HTML 5 檔案架構。由此,在"你-APP-的名字.herokuapp.com"這個網址創造出了自己的網頁。明天要學更多與網頁元素相關的東西,並練習藉由內容傳遞網路(Content Delivery Network或Content Distribution Network,縮寫:CDN)來借用其他人提供的資源。謝謝大家今天的觀看,我會將今天的程式碼放到 Github 上,有興趣的可以點這裡過去參考參考。另外,若對今天的內容有疑問或想再多討論一些的,也非常歡迎在下面留言。謝謝大家!
➀ Flask 官方資源
➁ 2019鐵人賽 慢慢帶你了解 Flask
➂ W3Schools HTML 5 標籤一覽表
➃ W3Schools HTML 5 線上教學
➄ FreeCodeCamp 線上學習
註:對於此系列文有興趣的讀者,歡迎參考由此系列文擴編成書的 LINE Bot by Python,以及最新的系列文《賴田捕手:追加篇》
第 31 天 初始化 LINE BOT on Heroku
第 32 天 快速回覆 QuickReply 介紹
第 33 天 妥善運用 Heroku APP 暫存空間
第 34 天 妥善運用 LINE Notify 免費推播
第 35 天 製造 Deploy to Heroku 按鈕